package org.lemon.db.store;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @since 2024-12-12 12:06
 */
public class BufferManager {

    private List<Buffer> bufferPool = new ArrayList<>();
    private Lock mu = new ReentrantLock();
    private int availableNum;

    public BufferManager(FileManager fileManager,LogManager logManager,int poolSize){
        this.availableNum = poolSize;
        for(int i = 0 ; i< poolSize ;i++){
            Buffer buffer = new Buffer(fileManager, logManager);
            bufferPool.add(buffer);
        }
    }

    public int available(){
        try {
            mu.lock();
            return availableNum;
        } finally {
            mu.unlock();
        }
    }

    public void flushAll(int txnNum){
        try {
            mu.lock();
            for (Buffer buffer:bufferPool){
                if(buffer.getTxnNum() == txnNum){
                    buffer.flush();
                }
            }
        } finally {
            mu.unlock();
        }
    }

    public Buffer tryPin(BlockId blockId){
        Buffer buffer = findExistingBuffer(blockId);

        if(buffer == null){
            buffer = chooseUnpinBuffer();
            if(buffer == null){
                return  buffer;
            }
            buffer.assignToBlock(blockId);
        }

        if(!buffer.isPined()){
            this.availableNum -= 1;
        }

        buffer.pin();
        return buffer;
    }

    public Buffer chooseUnpinBuffer(){
        for(Buffer buffer: bufferPool){
            if(!buffer.isPined()){
                return buffer;
            }
        }
        return null;
    }

    public Buffer findExistingBuffer(BlockId blockId){
        for(Buffer buffer: bufferPool){
            BlockId b = buffer.getBlockId();
            if(b != null && b.equals(blockId)){
                return buffer;
            }
        }
        return null;

    }

    public Buffer pin(BlockId blockId){
        try {
            mu.lock();
            long start = System.currentTimeMillis();
            Buffer buffer = tryPin(blockId);
            for(;buffer == null && !waitingTooLong(start);){
                //如果无法获得缓存页面，那么让调用者等待一段时间后再次尝试
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //
                }
                buffer = tryPin(blockId);
                if(buffer == null){
                    throw new RuntimeException("No buffer available , careful for dead lock");
                }
            }

            return buffer;
        } finally {
            mu.unlock();
        }
    }

    public void unpin(Buffer buffer){
        if (buffer == null){
            return;
        }
        try {
            mu.lock();
            buffer.unpin();
            if(!buffer.isPined()) {
                this.availableNum += 1;
            }
        } finally {
            mu.unlock();
        }
    }

    private boolean waitingTooLong(long start){
        long elapsed = System.currentTimeMillis() - start;
        if(elapsed >= 3000){
            return true;
        }

        return false;
    }

}
